/**
 * @class Ext.Button
 * @extends Ext.BoxComponent Simple Button class
 * @cfg {String} text The button text to be used as innerHTML (html tags are accepted)
 * @cfg {String} icon The path to an image to display in the button (the image will be set as the background-image CSS property of the button by default, so if
 *      you want a mixed icon/text button, set cls:'x-btn-text-icon')
 * @cfg {Function} handler A function called when the button is clicked (can be used instead of click event). The handler is passed the following parameters:<div
 *      class="mdetail-params"><ul> <li><code>b</code> : Button<div class="sub-desc">This Button.</div></li> <li><code>e</code> : EventObject<div
 *      class="sub-desc">The click event.</div></li> </ul></div>
 * @cfg {Number} minWidth The minimum width for this button (used to give a set of buttons a common width). See also {@link Ext.Panel}.<tt>{@link Ext.Panel#minButtonWidth minButtonWidth}</tt>.
 * @cfg {String/Object} tooltip The tooltip for the button - can be a string to be used as innerHTML (html tags are accepted) or QuickTips config object
 * @cfg {Boolean} hidden True to start hidden (defaults to false)
 * @cfg {Boolean} disabled True to start disabled (defaults to false)
 * @cfg {Boolean} pressed True to start pressed (only if enableToggle = true)
 * @cfg {String} toggleGroup The group this toggle button is a member of (only 1 per group can be pressed)
 * @cfg {Boolean/Object} repeat True to repeat fire the click event while the mouse is down. This can also be a {@link Ext.util.ClickRepeater ClickRepeater}
 *      config object (defaults to false).
 * @constructor Create a new button
 * @param {Object} config The config object
 * @xtype button
 */
Ext.Button = Ext.extend(Ext.BoxComponent,
{
	hidden: false,
	disabled: false,
	pressed: false,
	enableToggle: false,
	menuAlign: 'tl-bl?',
	type: 'button',

	// private
	menuClassTarget: 'tr:nth(2)',
	clickEvent: 'click',
	handleMouseEvents: true,
	tooltipType: 'qtip',
	buttonSelector: 'button:first-child',
	scale: 'small',
	iconAlign: 'left',
	arrowAlign: 'right',

	initComponent: function()
	{
		if( this.menu )
		{
			// If array of items, turn it into an object config so we
			// can set the ownerCt property in the config
			if( Ext.isArray(this.menu) )
			{
				this.menu =
				{
					items: this.menu
				};
			}

			// An object config will work here, but an instance of a menu
			// will have already setup its ref's and have no effect
			if( Ext.isObject(this.menu) )
			{
				this.menu.ownerCt = this;
			}

			this.menu = Ext.menu.MenuMgr.get(this.menu);
			this.menu.ownerCt = undefined;
		}

		Ext.Button.superclass.initComponent.call(this);

		this.addEvents('click', 'toggle', 'mouseover', 'mouseout', 'menushow', 'menuhide', 'menutriggerover', 'menutriggerout');

		if( Ext.isString(this.toggleGroup) )
		{
			this.enableToggle = true;
		}
	},

	/**
	 * <p>This method returns an Array which provides substitution parameters for the {@link #template Template} used to create this Button's DOM structure.</p>
	 * <p>Instances or subclasses which use a different Template to create a different DOM structure may need to provide their own implementation of this method.</p>
	 * <p>The default implementation which provides data for the default {@link #template} returns an Array containing the following items:</p><div
	 * class="mdetail-params"><ul> <li>The &lt;button&gt;'s {@link #type}</li> <li>A CSS class name applied to the Button's main &lt;tbody&gt; element which
	 * determines the button's scale and icon alignment.</li> <li>A CSS class to determine the presence and position of an arrow icon. (<code>'x-btn-arrow'</code>
	 * or <code>'x-btn-arrow-bottom'</code> or <code>''</code>)</li> <li>The {@link #cls} CSS class name applied to the button's wrapping &lt;table&gt;
	 * element.</li> <li>The Component id which is applied to the button's wrapping &lt;table&gt; element.</li> </ul></div>
	 * 
	 * @return {Array} Substitution data for a Template.
	 */
	getTemplateArgs: function()
	{
		return
		[
		    this.type, 'x-btn-' + this.scale + ' x-btn-icon-' + this.scale + '-' + this.iconAlign, this.getMenuClass(), this.cls, this.id
		];
	},

	// private
	setButtonClass: function()
	{
		if( this.useSetClass )
		{
			if( !Ext.isEmpty(this.oldCls) )
			{
				this.el.removeClass(
				[
				    this.oldCls, 'x-btn-pressed'
				]);
			}
			this.oldCls = (this.iconCls || this.icon) ? (this.text ? 'x-btn-text-icon' : 'x-btn-icon') : 'x-btn-noicon';
			this.el.addClass(
			[
			    this.oldCls, this.pressed ? 'x-btn-pressed' : null
			]);
		}
	},

	// protected
	getMenuClass: function()
	{
		return this.menu ? (this.arrowAlign != 'bottom' ? 'x-btn-arrow' : 'x-btn-arrow-bottom') : '';
	},

	// private
	onRender: function( ct, position )
	{
		if( !this.template )
		{
			if( !Ext.Button.buttonTemplate )
			{
				// hideous table template
				Ext.Button.buttonTemplate =
				new Ext.Template('<table id="{4}" cellspacing="0" class="x-btn {3}"><tbody class="{1}">', '<tr><td class="x-btn-tl"><i>&#160;</i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i>&#160;</i></td></tr>',
				'<tr><td class="x-btn-ml"><i>&#160;</i></td><td class="x-btn-mc"><em class="{2} x-unselectable" unselectable="on"><button type="{0}"></button></em></td><td class="x-btn-mr"><i>&#160;</i></td></tr>',
				'<tr><td class="x-btn-bl"><i>&#160;</i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i>&#160;</i></td></tr>', '</tbody></table>');
				Ext.Button.buttonTemplate.compile();
			}
			this.template = Ext.Button.buttonTemplate;
		}

		var btn, targs = this.getTemplateArgs();

		if( position )
		{
			btn = this.template.insertBefore(position, targs, true);
		}
		else
		{
			btn = this.template.append(ct, targs, true);
		}
		/**
		 * An {@link Ext.Element Element} encapsulating the Button's clickable element. By default, this references a <tt>&lt;button&gt;</tt> element. Read only.
		 * 
		 * @type Ext.Element
		 * @property btnEl
		 */
		this.btnEl = btn.child(this.buttonSelector);
		this.mon(this.btnEl,
		{
			scope: this,
			focus: this.onFocus,
			blur: this.onBlur
		});

		this.initButtonEl(btn, this.btnEl);

		Ext.ButtonToggleMgr.register(this);
	},

	// private
	initButtonEl: function( btn, btnEl )
	{
		this.el = btn;
		this.setIcon(this.icon);
		this.setText(this.text);
		this.setIconClass(this.iconCls);
		if( Ext.isDefined(this.tabIndex) )
		{
			btnEl.dom.tabIndex = this.tabIndex;
		}
		if( this.tooltip )
		{
			this.setTooltip(this.tooltip, true);
		}

		if( this.handleMouseEvents )
		{
			this.mon(btn,
			{
				scope: this,
				mouseover: this.onMouseOver,
				mousedown: this.onMouseDown
			});

			// new functionality for monitoring on the document level
			// this.mon(btn, 'mouseout', this.onMouseOut, this);
		}

		if( this.menu )
		{
			this.mon(this.menu,
			{
				scope: this,
				show: this.onMenuShow,
				hide: this.onMenuHide
			});
		}

		if( this.repeat )
		{
			var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {});
			this.mon(repeater, 'click', this.onRepeatClick, this);
		}
		else
		{
			this.mon(btn, this.clickEvent, this.onClick, this);
		}
	},

	// private
	afterRender: function()
	{
		Ext.Button.superclass.afterRender.call(this);
		this.useSetClass = true;
		this.setButtonClass();
		this.doc = Ext.getDoc();
		this.doAutoWidth();
	},

	/**
	 * Sets the CSS class that provides a background image to use as the button's icon. This method also changes the value of the {@link iconCls} config
	 * internally.
	 * 
	 * @param {String} cls The CSS class providing the icon image
	 * @return {Ext.Button} this
	 */
	setIconClass: function( cls )
	{
		this.iconCls = cls;
		if( this.el )
		{
			this.btnEl.dom.className = '';
			this.btnEl.addClass(
			[
			    'x-btn-text', cls || ''
			]);
			this.setButtonClass();
		}
		return this;
	},

	/**
	 * Sets the tooltip for this Button.
	 * 
	 * @param {String/Object} tooltip. This may be:<div class="mdesc-details"><ul> <li><b>String</b> : A string to be used as innerHTML (html tags are
	 *        accepted) to show in a tooltip</li> <li><b>Object</b> : A configuration object for {@link Ext.QuickTips#register}.</li> </ul></div>
	 * @return {Ext.Button} this
	 */
	setTooltip: function( tooltip, /* private */initial )
	{
		if( this.rendered )
		{
			if( !initial )
			{
				this.clearTip();
			}
			if( Ext.isObject(tooltip) )
			{
				Ext.QuickTips.register(Ext.apply(
				{
					target: this.btnEl.id
				}, tooltip));
				this.tooltip = tooltip;
			}
			else
			{
				this.btnEl.dom[this.tooltipType] = tooltip;
			}
		}
		else
		{
			this.tooltip = tooltip;
		}
		return this;
	},

	// private
	clearTip: function()
	{
		if( Ext.isObject(this.tooltip) )
		{
			Ext.QuickTips.unregister(this.btnEl);
		}
	},

	// private
	beforeDestroy: function()
	{
		if( this.rendered )
		{
			this.clearTip();
		}
		if( this.menu && this.destroyMenu !== false )
		{
			Ext.destroy(this.btnEl, this.menu);
		}
		Ext.destroy(this.repeater);
	},

	// private
	onDestroy: function()
	{
		if( this.rendered )
		{
			this.doc.un('mouseover', this.monitorMouseOver, this);
			this.doc.un('mouseup', this.onMouseUp, this);
			delete this.doc;
			delete this.btnEl;
			Ext.ButtonToggleMgr.unregister(this);
		}
		Ext.Button.superclass.onDestroy.call(this);
	},

	// private
	doAutoWidth: function()
	{
		if( this.autoWidth !== false && this.el && this.text && this.width === undefined )
		{
			this.el.setWidth('auto');
			if( Ext.isIE7 && Ext.isStrict )
			{
				var ib = this.btnEl;
				if( ib && ib.getWidth() > 20 )
				{
					ib.clip();
					ib.setWidth(Ext.util.TextMetrics.measure(ib, this.text).width + ib.getFrameWidth('lr'));
				}
			}
			if( this.minWidth )
			{
				if( this.el.getWidth() < this.minWidth )
				{
					this.el.setWidth(this.minWidth);
				}
			}
		}
	},

	/**
	 * Assigns this Button's click handler
	 * 
	 * @param {Function} handler The function to call when the button is clicked
	 * @param {Object} scope (optional) The scope (<code>this</code> reference) in which the handler function is executed. Defaults to this Button.
	 * @return {Ext.Button} this
	 */
	setHandler: function( handler, scope )
	{
		this.handler = handler;
		this.scope = scope;
		return this;
	},

	/**
	 * Sets this Button's text
	 * 
	 * @param {String} text The button text
	 * @return {Ext.Button} this
	 */
	setText: function( text )
	{
		this.text = text;
		if( this.el )
		{
			this.btnEl.update(text || '&#160;');
			this.setButtonClass();
		}
		this.doAutoWidth();
		return this;
	},

	/**
	 * Sets the background image (inline style) of the button. This method also changes the value of the {@link icon} config internally.
	 * 
	 * @param {String} icon The path to an image to display in the button
	 * @return {Ext.Button} this
	 */
	setIcon: function( icon )
	{
		this.icon = icon;
		if( this.el )
		{
			this.btnEl.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
			this.setButtonClass();
		}
		return this;
	},

	/**
	 * Gets the text for this Button
	 * 
	 * @return {String} The button text
	 */
	getText: function()
	{
		return this.text;
	},

	/**
	 * If a state it passed, it becomes the pressed state otherwise the current state is toggled.
	 * 
	 * @param {Boolean} state (optional) Force a particular state
	 * @param {Boolean} supressEvent (optional) True to stop events being fired when calling this method.
	 * @return {Ext.Button} this
	 */
	toggle: function( state, suppressEvent )
	{
		state = state === undefined ? !this.pressed : !!state;
		if( state != this.pressed )
		{
			if( this.rendered )
			{
				this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed');
			}
			this.pressed = state;
			if( !suppressEvent )
			{
				this.fireEvent('toggle', this, state);
				if( this.toggleHandler )
				{
					this.toggleHandler.call(this.scope || this, this, state);
				}
			}
		}
		return this;
	},

	// private
	onDisable: function()
	{
		this.onDisableChange(true);
	},

	// private
	onEnable: function()
	{
		this.onDisableChange(false);
	},

	onDisableChange: function( disabled )
	{
		if( this.el )
		{
			if( !Ext.isIE6 || !this.text )
			{
				this.el[disabled ? 'addClass' : 'removeClass'](this.disabledClass);
			}
			this.el.dom.disabled = disabled;
		}
		this.disabled = disabled;
	},

	/**
	 * Show this button's menu (if it has one)
	 */
	showMenu: function()
	{
		if( this.rendered && this.menu )
		{
			if( this.tooltip )
			{
				Ext.QuickTips.getQuickTip().cancelShow(this.btnEl);
			}
			if( this.menu.isVisible() )
			{
				this.menu.hide();
			}
			this.menu.ownerCt = this;
			this.menu.show(this.el, this.menuAlign);
		}
		return this;
	},

	/**
	 * Hide this button's menu (if it has one)
	 */
	hideMenu: function()
	{
		if( this.hasVisibleMenu() )
		{
			this.menu.hide();
		}
		return this;
	},

	/**
	 * Returns true if the button has a menu and it is visible
	 * 
	 * @return {Boolean}
	 */
	hasVisibleMenu: function()
	{
		return this.menu && this.menu.ownerCt == this && this.menu.isVisible();
	},

	// private
	onRepeatClick: function( repeat, e )
	{
		this.onClick(e);
	},

	// private
	onClick: function( e )
	{
		if( e )
		{
			e.preventDefault();
		}
		if( e.button !== 0 )
		{
			return;
		}
		if( !this.disabled )
		{
			this.doToggle();
			if( this.menu && !this.hasVisibleMenu() && !this.ignoreNextClick )
			{
				this.showMenu();
			}
			this.fireEvent('click', this, e);
			if( this.handler )
			{
				// this.el.removeClass('x-btn-over');
				this.handler.call(this.scope || this, this, e);
			}
		}
	},

	// private
	doToggle: function()
	{
		if( this.enableToggle && (this.allowDepress !== false || !this.pressed) )
		{
			this.toggle();
		}
	},

	// private
	isMenuTriggerOver: function( e, internal )
	{
		return this.menu && !internal;
	},

	// private
	isMenuTriggerOut: function( e, internal )
	{
		return this.menu && !internal;
	},

	// private
	onMouseOver: function( e )
	{
		if( !this.disabled )
		{
			var internal = e.within(this.el, true);
			if( !internal )
			{
				this.el.addClass('x-btn-over');
				if( !this.monitoringMouseOver )
				{
					this.doc.on('mouseover', this.monitorMouseOver, this);
					this.monitoringMouseOver = true;
				}
				this.fireEvent('mouseover', this, e);
			}
			if( this.isMenuTriggerOver(e, internal) )
			{
				this.fireEvent('menutriggerover', this, this.menu, e);
			}
		}
	},

	// private
	monitorMouseOver: function( e )
	{
		if( e.target != this.el.dom && !e.within(this.el) )
		{
			if( this.monitoringMouseOver )
			{
				this.doc.un('mouseover', this.monitorMouseOver, this);
				this.monitoringMouseOver = false;
			}
			this.onMouseOut(e);
		}
	},

	// private
	onMouseOut: function( e )
	{
		var internal = e.within(this.el) && e.target != this.el.dom;
		this.el.removeClass('x-btn-over');
		this.fireEvent('mouseout', this, e);
		if( this.isMenuTriggerOut(e, internal) )
		{
			this.fireEvent('menutriggerout', this, this.menu, e);
		}
	},

	focus: function()
	{
		this.btnEl.focus();
	},

	blur: function()
	{
		this.btnEl.blur();
	},

	// private
	onFocus: function( e )
	{
		if( !this.disabled )
		{
			this.el.addClass('x-btn-focus');
		}
	},
	// private
	onBlur: function( e )
	{
		this.el.removeClass('x-btn-focus');
	},

	// private
	getClickEl: function( e, isUp )
	{
		return this.el;
	},

	// private
	onMouseDown: function( e )
	{
		if( !this.disabled && e.button === 0 )
		{
			this.getClickEl(e).addClass('x-btn-click');
			this.doc.on('mouseup', this.onMouseUp, this);
		}
	},
	// private
	onMouseUp: function( e )
	{
		if( e.button === 0 )
		{
			this.getClickEl(e, true).removeClass('x-btn-click');
			this.doc.un('mouseup', this.onMouseUp, this);
		}
	},
	// private
	onMenuShow: function( e )
	{
		if( this.menu.ownerCt == this )
		{
			this.menu.ownerCt = this;
			this.ignoreNextClick = 0;
			this.el.addClass('x-btn-menu-active');
			this.fireEvent('menushow', this, this.menu);
		}
	},
	// private
	onMenuHide: function( e )
	{
		if( this.menu.ownerCt == this )
		{
			this.el.removeClass('x-btn-menu-active');
			this.ignoreNextClick = this.restoreClick.defer(250, this);
			this.fireEvent('menuhide', this, this.menu);
			delete this.menu.ownerCt;
		}
	},

	// private
	restoreClick: function()
	{
		this.ignoreNextClick = 0;
	}

	/**
	 * @cfg {String} autoEl
	 * @hide
	 */
	/**
	 * @cfg {String/Object} html
	 * @hide
	 */
	/**
	 * @cfg {String} contentEl
	 * @hide
	 */
	/**
	 * @cfg {Mixed} data
	 * @hide
	 */
	/**
	 * @cfg {Mixed} tpl
	 * @hide
	 */
	/**
	 * @cfg {String} tplWriteMode
	 * @hide
	 */
});
Ext.reg('button', Ext.Button);

// Private utility class used by Button
Ext.ButtonToggleMgr = function()
{
	var groups = {};

	function toggleGroup( btn, state )
	{
		if( state )
		{
			var g = groups[btn.toggleGroup];
			for( var i = 0, l = g.length; i < l; i++ )
			{
				if( g[i] != btn )
				{
					g[i].toggle(false);
				}
			}
		}
	}

	return {
		register: function( btn )
		{
			if( !btn.toggleGroup )
			{
				return;
			}
			var g = groups[btn.toggleGroup];
			if( !g )
			{
				g = groups[btn.toggleGroup] = [];
			}
			g.push(btn);
			btn.on('toggle', toggleGroup);
		},

		unregister: function( btn )
		{
			if( !btn.toggleGroup )
			{
				return;
			}
			var g = groups[btn.toggleGroup];
			if( g )
			{
				g.remove(btn);
				btn.un('toggle', toggleGroup);
			}
		},

		/**
		 * Gets the pressed button in the passed group or null
		 * 
		 * @param {String} group
		 * @return Button
		 */
		getPressed: function( group )
		{
			var g = groups[group];
			if( g )
			{
				for( var i = 0, len = g.length; i < len; i++ )
				{
					if( g[i].pressed === true )
					{
						return g[i];
					}
				}
			}
			return null;
		}
	};
}();
